package com.fingard.dsp.bank.directbank.cmb06;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fingard.common.utils.json.JSONUtils;
import com.fingard.constant.Format;
import com.fingard.dsp.bank.directConfig.ActSetItem;
import com.fingard.dsp.bank.directbank.DirectBase;
import com.fingard.dsp.bank.directbank.cmb06.util.*;
import com.fingard.text.StringHelper;
import com.fingard.util.TransIDHelper;
import okhttp3.*;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class CMB06Base extends DirectBase {
    private static TransIDHelper idHelper = new TransIDHelper(1, 999);

    protected String getQryTransID() {
        Date tmpDateNow = new Date();
        return Format.DateTime12Year2Format.format(tmpDateNow) + idHelper.getNextIDStr();
    }

    protected String getCharset() {
        return getBankConfig().getCharset("UTF-8");
    }

    public OkHttpClient getClient () {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            X509TrustManager x509TrustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted (X509Certificate[] chain, String authType) {
                }

                @Override
                public void checkServerTrusted (X509Certificate[] chain, String authType) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers () {
                    return new X509Certificate[]{};
                }
            };
            TrustManager[] trustAllCerts = new TrustManager[]{x509TrustManager};
            sslContext.init(null, trustAllCerts, new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            OkHttpClient client = new OkHttpClient.Builder()
                    .readTimeout(60, TimeUnit.SECONDS)
//                    忽略证书验证，根据安全性需要选择
                    .sslSocketFactory(sslSocketFactory, x509TrustManager)
                    .hostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify (String s, SSLSession sslSession) {
                            return true;
                        }
                    })
                    .build();
            return client;
        } catch (Exception e) {
            throw new RuntimeException("Http客户端构造失败");
        }
    }

    public String[] sendToBank(JSONObject tmpReqJson, ActSetItem actItem, String transactionCode) throws Exception {
        String[] tmpStrRet = new String[]{"", ""};

        String reqJson = JSON.toJSONString(tmpReqJson, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat);
        WriteBankLogLn2("发送请求报文明文:\r\n" + reqJson);

        CMBSignInfo cmbSignInfo = new CMBSignInfo();
        cmbSignInfo.setAppid(actItem.appId);
        cmbSignInfo.setAppsecret(actItem.appSecret);
        cmbSignInfo.setCmbPublicyKey(actItem.ownCertFilePath.toUpperCase());
        cmbSignInfo.setPrivateKey(actItem.ownKeyStorePath.toUpperCase());
        cmbSignInfo.setJwtPrivateKey(actItem.ownKeyStorePath.toUpperCase());
        cmbSignInfo.setDomainName(getBankConfig().getValueAsString("issuer"));
        cmbSignInfo.setVerify("SM3withSM2");
        cmbSignInfo.setUrl(bankFront.serverURL);

        String timestamp = String.valueOf(new Date().getTime() / 1000);
        //接口业务参数
        //建议对业务参数进行base64编码，业务参数可用于业务系统的验证，防止参数篡改
        String sign = new String(Base64.encode(JSON.toJSONString(new JSONObject()).getBytes()));
        //拼接参数
        String src = "appid=" + cmbSignInfo.getAppid() + "&" + "secret=" + cmbSignInfo.getAppsecret() + "&" + "sign=" + sign + "&" + "timestamp=" + timestamp;
        WriteBankLogLn("apisign签名原文:" + src);
        String apisign = Hex.toHexString(SM2Util.sm3withsm2Signature2(cmbSignInfo.getPrivateKey(), src));
        WriteBankLogLn("apisign签名结果:" + src);
        JWTUtil jwtUtil = new JWTUtil();
        String authorization = jwtUtil.createToken(cmbSignInfo, transactionCode);
        WriteBankLogLn("JWT-TOKEN 签名: " + authorization);
        String datagramSign = new String(Base64.encode(SM2Util.sm3withsm2Signature2(cmbSignInfo.getPrivateKey(), reqJson)));
        String sm4Key = SM4Util.generateKey();
        WriteBankLogLn("sm4密钥:" + sm4Key);
        WriteBankLogLn("招行公钥:" + cmbSignInfo.getCmbPublicyKey());
        String datagramKey = new String(Base64.encode(SM2Util.sm2Encrypt(Util.hexStr2Byte(sm4Key), SM2Util.getPublickey(cmbSignInfo.getCmbPublicyKey()))));
        byte[] tmpStrSend = SM4Util.encryptEcb(sm4Key, reqJson);
        WriteBankLogLn("发送银行请求json密文:\r\n" + tmpStrSend);
        RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), tmpStrSend);
        Request request = new Request.Builder()
                .url(cmbSignInfo.getUrl())
                .addHeader("appid", cmbSignInfo.getAppid())
                .addHeader("timestamp", timestamp)
                .addHeader("sign", sign)
                .addHeader("apisign", apisign)
                .addHeader("verify", cmbSignInfo.getVerify())
                .addHeader("X-Gateway-TransactionCode", transactionCode)
                .addHeader("Authorization", authorization)
                .addHeader("X-Datagram-Signature", datagramSign)
                .addHeader("X-Datagram-Secret-Key", datagramKey)
                .post(body)
                .build();
        WriteBankLogLn("http请求头: \nContent-Type:" + "application/json;charset=utf-8");
        WriteBankLogLn("X-Gateway-TransactionCode：" + transactionCode);
        WriteBankLogLn("Authorization：" + authorization);
        WriteBankLogLn("X-Datagram-Signature：" + datagramSign);
        WriteBankLogLn("X-Datagram-Secret-Key：" + datagramKey);
        Response response = getClient().newCall(request).execute();
        try {
            // 解密
            byte[] bodyBytes = response.body().bytes();
            String tmpRespBody = new String(bodyBytes,getCharset());
            if (StringHelper.hasAnyChar(tmpRespBody) && tmpRespBody.startsWith("{")) {
                WriteBankLogLn("银行返回明文报文:\r\n"+tmpRespBody);
                JSONObject tmpRespJson = (JSONObject) JSONObject.parse(tmpRespBody);
                if (tmpRespJson.containsKey("errCode")) {
                    // 异常返回
                    tmpStrRet[0] = "-1";
                    tmpStrRet[1] = tmpRespJson.getString("errCode") + "+" + tmpRespJson.getString("respMsg");
                } else if (tmpStrRet[1].contains("ERRORMSG")) {
                    // 异常返回
                    tmpStrRet[0] = "-1";
                    tmpStrRet[1] = tmpRespJson.getJSONObject("BODY").getJSONArray("$ERRORMSG$").getJSONObject(0).getString("xErrMsg");
                }
            } else {
                String respSeceretKey = response.header("X-Datagram-Secret-Key");
                WriteBankLogLn("响应报文头的X-Datagram-Secret-Key:" + respSeceretKey);
                respSeceretKey = SM2Util.toHexString(SM2Util.sm2Decrypt(Base64.decode(respSeceretKey), SM2Util.getPrivatekey(cmbSignInfo.getPrivateKey())));
                WriteBankLogLn("解密后的X-Datagram-Secret-Key:"+respSeceretKey);

                WriteBankLogLn("银行返回密文结果：\r\n" + new String(bodyBytes, getCharset()));
                String respPlainText = SM4Util.decryptEcb(respSeceretKey, bodyBytes);

                JSONObject formatJson =  JSONObject.parseObject(respPlainText);
                respPlainText = JSON.toJSONString(formatJson, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat);
                WriteBankLogStep3(respPlainText);
                tmpStrRet[1] = respPlainText;
            }
        } catch (Exception ex) {
            tmpStrRet[0] = "-1";
            tmpStrRet[1] = "解密失败:" + ex.getMessage();
            ex.printStackTrace();
            WriteBankLogLn(ex);
            response.close();
            return tmpStrRet;
        }
        response.close();
        return tmpStrRet;
    }
}